<?php declare(strict_types=1);
/**
 * @author      xianganyall <xianganyall@gmail.com>
 * @copyright   2023-2025 owner
 **/

namespace Srv\Libs\Plugins\Gif;

class GifEncoder
{
    private string $GIF     = 'GIF89a';
    private array $BUF      = [];
    private int $LOP        =  0;
    private int $DIS        =  2;
    private int $COL        = -1;
    private int $IMG        = -1;

    /**
     * @param array $imageGiftList
     * @param int $dlyTime
     * @param int $isLoop
     * @return string
     * getGifByImageStream
     */
    public function getGifByImageStream(array $imageGiftList, int $dlyTime = 100, int $isLoop = 0):string
    {
        if(!$this->encoder($imageGiftList, $dlyTime, $isLoop, 0, 0, 0, 0, 'bin')) return '';
        return $this->GetAnimation();
    }

    /**
     * @param array $fileGiftList
     * @param int $dlyTime
     * @param int $isLoop
     * @return string
     * getGifByFilePath
     */
    public function getGifByFilePath(array $fileGiftList, int $dlyTime = 100, int $isLoop = 0):string
    {
        if(!$this->encoder($fileGiftList, $dlyTime, $isLoop, 0, 0, 0, 0, 'url')) return '';
        return $this->GetAnimation();
    }

    /**
     * @param array $GIF_src
     * @param int $GIF_dly
     * @param int $GIF_lop
     * @param int $GIF_dis
     * @param int $GIF_red
     * @param int $GIF_grn
     * @param int $GIF_blu
     * @param string $GIF_mod
     * @return bool
     * encoder
     */
    private function encoder(array $GIF_src, int $GIF_dly = 100, int $GIF_lop = 0, int $GIF_dis = 0,  int $GIF_red = 0, int $GIF_grn = 0, int $GIF_blu = 0, string $GIF_mod = 'bin'):bool
    {
        $GIF_mod    = strtolower($GIF_mod);
        $this->LOP  = ($GIF_lop > -1) ? $GIF_lop : 0;
        $this->DIS  = ($GIF_dis > -1) ? (( $GIF_dis < 3) ? $GIF_dis : 3) : 2;
        $this->COL  = ($GIF_red > -1 && $GIF_grn > -1 && $GIF_blu > -1) ? ($GIF_red | ($GIF_grn << 8) | ($GIF_blu << 16)) : -1;
        for ($i = 0, $src_count = count($GIF_src); $i < $src_count; $i++){
            if($GIF_mod === 'url'){
                $this->BUF[] = fread(fopen($GIF_src[$i], 'rb'), filesize ($GIF_src[$i]));
            }elseif($GIF_mod === 'bin'){
                $this->BUF[] = $GIF_src[$i];
            }else{
                return false;
            }
            if(substr($this->BUF[$i], 0, 6) !== 'GIF87a' && substr($this->BUF[$i], 0, 6) !== 'GIF89a') return false;
            for ($j = (13 + 3 * (2 << (ord(substr($this->BUF[$i], 10, 1)) & 0x07))), $k = true; $k; $j++){
                switch (substr($this->BUF[$i], $j, 1)){
                    case '!':{
                        if((substr($this->BUF[$i], ($j + 3), 8)) === 'NETSCAPE') return false;
                        break;
                    }
                    case ';':{
                        $k  = false;
                        break;
                    }
                }
            }
        }
        $this->GIFAddHeader();
        for($i = 0, $count_buf = count($this->BUF); $i < $count_buf; $i++){
            if(is_array($GIF_dly)){
                $dlyTime = intval($GIF_dly[$i]??$GIF_dly[0]);
                if($dlyTime <= 0) $dlyTime = 100;
            }else{
                $dlyTime = $GIF_dly;
            }
            $this->GIFAddFrames($i, $dlyTime);
        }
        $this->GIFAddFooter();
        return true;
    }

    /**
     * @return void
     * GIFAddHeader
     */
    private function GIFAddHeader()
    {
        if(ord(substr($this->BUF[0], 10, 1)) & 0x80){
            $cMap       = 3 *(2 <<(ord(substr($this->BUF[0], 10, 1)) & 0x07));
            $this->GIF  .= substr($this->BUF[0], 6, 7);
            $this->GIF  .= substr($this->BUF[0], 13, $cMap);
            $this->GIF  .= "!\377\13NETSCAPE2.0\3\1" . $this->GIFWord($this->LOP) . "\0";
        }
    }

    /**
     * @param int $i
     * @param int $d
     * @return void
     * GIFAddFrames
     */
    private function GIFAddFrames(int $i, int $d)
    {
        $Locals_str = 13 + 3 * (2 <<(ord(substr($this->BUF[$i], 10, 1)) & 0x07));
        $Locals_end = strlen($this->BUF[$i]) - $Locals_str - 1;
        $Locals_tmp = substr ($this->BUF[$i], $Locals_str, $Locals_end);
        $Global_len = 2 << (ord(substr($this->BUF[0], 10, 1)) & 0x07);
        $Locals_len = 2 << (ord(substr($this->BUF[$i], 10, 1)) & 0x07);
        $Global_rgb = substr($this->BUF[0], 13, 3 * (2 <<(ord(substr($this->BUF[0], 10, 1)) & 0x07)));
        $Locals_rgb = substr($this->BUF[$i], 13, 3 * (2 <<(ord(substr($this->BUF[$i], 10, 1)) & 0x07)));
        $Locals_ext = "!\xF9\x04" . chr(($this->DIS << 2)) . chr(($d >> 0) & 0xFF) . chr(($d >> 8) & 0xFF) . "\x0\x0";
        if($this->COL > -1 && ord(substr($this->BUF[$i], 10, 1)) & 0x80){
            for($j = 0; $j < (2 << (ord(substr($this->BUF[$i], 10, 1)) & 0x07)); $j++){
                if(ord (substr($Locals_rgb, 3 * $j, 1)) == ($this->COL >>  0) & 0xFF && ord(substr($Locals_rgb, 3 * $j + 1, 1)) ==($this->COL >>  8) & 0xFF && ord(substr($Locals_rgb, 3 * $j + 2, 1)) ==($this->COL >> 16) & 0xFF){
                    $Locals_ext = "!\xF9\x04" . chr(($this->DIS << 2) + 1) . chr (( $d >> 0) & 0xFF) . chr (( $d >> 8) & 0xFF) . chr ($j) . "\x0";
                    break;
                }
            }
        }
        $Locals_img     = '';
        switch(substr($Locals_tmp, 0, 1)){
            case '!':{
                $Locals_img = substr($Locals_tmp, 8, 10);
                $Locals_tmp = substr($Locals_tmp, 18, strlen ($Locals_tmp) - 18);
                break;
            }
            case ',':{
                $Locals_img = substr($Locals_tmp, 0, 10);
                $Locals_tmp = substr($Locals_tmp, 10, strlen($Locals_tmp) - 10);
                break;
            }
        }
        if(ord(substr($this->BUF[$i], 10, 1)) & 0x80 && $this->IMG > -1){
            if($Global_len == $Locals_len){
                if($this->GIFBlockCompare($Global_rgb, $Locals_rgb, $Global_len)){
                    $this->GIF .=($Locals_ext . $Locals_img . $Locals_tmp);
                }else{
                    $byte  = ord(substr($Locals_img, 9, 1));
                    $byte |= 0x80;
                    $byte &= 0xF8;
                    $byte |=(ord(substr($this->BUF[0], 10, 1)) & 0x07);
                    $Locals_img     = substr($Locals_img, 0, 9).chr($byte).substr($Locals_img, 10);
                    $this->GIF .= ($Locals_ext . $Locals_img . $Locals_rgb . $Locals_tmp);
                }
            }else{
                $byte  = ord(substr($Locals_img, 9, 1));
                $byte |= 0x80;
                $byte &= 0xF8;
                $byte |= (ord(substr($this->BUF[$i], 10, 1)) & 0x07);
                $Locals_img     = substr($Locals_img, 0, 9).chr($byte).substr($Locals_img, 10);
                $this->GIF .= ($Locals_ext . $Locals_img . $Locals_rgb . $Locals_tmp);
            }
        }else{
            $this->GIF .=($Locals_ext . $Locals_img . $Locals_tmp);
        }
        $this->IMG  = 1;
    }

    /**
     * @return void
     * GIFAddFooter
     */
    private function GIFAddFooter()
    {
        $this->GIF .= ';';
    }

    /**
     * @param string $GlobalBlock
     * @param string $LocalBlock
     * @param int $Len
     * @return int
     * GIFBlockCompare
     */
    private function GIFBlockCompare(string $GlobalBlock, string $LocalBlock, int $Len):int
    {
        for($i = 0; $i < $Len; $i++) if(substr($GlobalBlock, 0, 3 * $i) != substr($LocalBlock, 3 * $i, 1) || substr($GlobalBlock, 3 * $i + 1, 1) != substr($LocalBlock, 3 * $i + 1, 1) || substr($GlobalBlock, 3 * $i + 2, 1) != substr($LocalBlock, 3 * $i + 2, 1)) return 0;
        return 1;
    }

    /**
     * @param int $int
     * @return string
     * GIFWord
     */
    private function GIFWord(int $int):string
    {
        return chr($int & 0xFF).chr(($int >> 8) & 0xFF);
    }

    /**
     * @return string
     * GetAnimation
     */
    private function GetAnimation():string
    {
        return $this->GIF;
    }
}